import { RabbitMQConfig } from '../src/types';
import * as dotenv from 'dotenv';
import { TestIsolation, TestTimeouts, TestEnvironment } from './utils/test-isolation';

// Load environment variables
dotenv.config();

export const TEST_CONFIG: RabbitMQConfig = {
  host: process.env.RABBITMQ_HOST || 'localhost',
  port: parseInt(process.env.RABBITMQ_PORT || '5672'),
  username: process.env.RABBITMQ_USERNAME || 'guest',
  password: process.env.RABBITMQ_PASSWORD || 'guest',
  virtualHost: process.env.RABBITMQ_VIRTUAL_HOST || '/',
  heartbeat: 600,
  connectionTimeout: 30000,
  sslEnabled: false,
  maxRetries: 3,
  retryDelay: 1000,
};

export const TEST_TIMEOUTS = {
  CONNECTION: TestTimeouts.CONNECTION,
  MESSAGE_PROCESSING: TestTimeouts.MESSAGE_PROCESSING,
  BATCH_PROCESSING: 15000,
  PERFORMANCE_TEST: 30000,
  CLEANUP: TestTimeouts.CLEANUP,
  UNIT_TEST: TestTimeouts.UNIT_TEST,
  INTEGRATION_TEST: TestTimeouts.INTEGRATION_TEST,
  E2E_TEST: TestTimeouts.E2E_TEST,
};

export const TEST_CONNECTION_POOL_CONFIG = {
  poolSize: 5,           // 基础池大小
  acquireTimeout: 15000, // 15秒获取超时 (更快失败)
  idleTimeout: 180000,   // 3分钟空闲超时 (更长保持)
  reapInterval: 30000,   // 30秒清理间隔 (更频繁清理)

  // 动态扩展配置
  minPoolSize: 2,        // 最小连接数
  maxPoolSize: 15,       // 最大连接数
  dynamicScaling: true,  // 启用动态扩展
  scaleUpThreshold: 3,   // 3个等待请求时扩展
  scaleDownThreshold: 8, // 8个空闲连接时收缩
  scaleUpStep: 2,        // 每次扩展2个连接
  scaleDownStep: 1,      // 每次收缩1个连接
};

export const TEST_QUEUES = {
  WORK_QUEUE: 'test_work_queue',
  RETRY_QUEUE: 'test_retry_queue',
  VALIDATION_QUEUE: 'test_validation_queue',
  MIDDLEWARE_QUEUE: 'test_middleware_queue',
  ERROR_QUEUE: 'test_error_queue',
  PERFORMANCE_QUEUE: 'test_performance_queue',
  PUBSUB_EXCHANGE: 'test_pubsub_exchange',
  RPC_QUEUE: 'test_rpc_calculator',
  ROUTING_EXCHANGE: 'test_routing_exchange',
};

export const TEST_DATA = {
  SIMPLE_MESSAGE: { id: 1, message: 'Hello World' },
  COMPLEX_MESSAGE: {
    user: {
      id: 123,
      name: 'John Doe',
      email: 'john@example.com',
      profile: {
        age: 30,
        preferences: ['email', 'sms'],
        metadata: {
          lastLogin: new Date().toISOString(),
          loginCount: 42,
        },
      },
    },
    action: 'user_update',
    timestamp: Date.now(),
  },
  BATCH_MESSAGES: Array.from({ length: 10 }, (_, i) => ({
    id: i + 1,
    type: 'batch_test',
    data: `Message ${i + 1}`,
    timestamp: Date.now(),
  })),
  LARGE_MESSAGE: {
    id: 'large_message',
    data: 'x'.repeat(10000), // 10KB message
    metadata: Array.from({ length: 100 }, (_, i) => ({
      key: `field_${i}`,
      value: `value_${i}`,
    })),
  },
};

export const isRabbitMQAvailable = async (): Promise<boolean> => {
  // Skip RabbitMQ check in unit test mode
  if (TestEnvironment.getTestMode() === 'unit') {
    return false;
  }

  // Only check real RabbitMQ if explicitly requested
  if (process.env.USE_REAL_RABBITMQ !== 'true') {
    return false;
  }

  try {
    const { RabbitMQClient } = await import('../src');
    const client = RabbitMQClient.create(TEST_CONFIG);

    // Set a short timeout for connection check
    const timeoutPromise = new Promise((_, reject) => {
      setTimeout(() => reject(new Error('Connection timeout')), 3000);
    });

    await Promise.race([
      client.initialize(),
      timeoutPromise
    ]);

    await client.close();
    return true;
  } catch (error) {
    console.warn('RabbitMQ not available for testing:', error instanceof Error ? error.message : String(error));
    return false;
  }
};

export const createTestClient = async (forceReal: boolean = false) => {
  // Always use mock unless explicitly requested to use real RabbitMQ
  const shouldUseReal = forceReal && process.env.USE_REAL_RABBITMQ === 'true';

  if (!shouldUseReal) {
    const { createMockClient } = await import('./__mocks__/rabbitmq-mock');
    return createMockClient();
  }

  // Only create real client if explicitly requested and RabbitMQ is available
  const { RabbitMQClient } = await import('../src');
  const client = RabbitMQClient.create(TEST_CONFIG, TEST_CONNECTION_POOL_CONFIG);

  // Set timeout for initialization
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('Client initialization timeout')), 5000);
  });

  await Promise.race([
    client.initialize(),
    timeoutPromise
  ]);

  return client;
};

export const createIsolatedTestContext = (testName?: string) => {
  return TestIsolation.createTestContext(testName);
};

export const cleanupTestQueues = async (client: any, queueNames?: string[]) => {
  const errors: Error[] = [];

  try {
    // Skip cleanup for mock clients
    if (!client.connectionPool) {
      return;
    }

    const connection = await client.connectionPool.getConnection();
    const channel = connection.channel;

    // Delete specified queues or all test queues
    const queuesToDelete = queueNames || Object.values(TEST_QUEUES);
    for (const queueName of queuesToDelete) {
      try {
        await channel.deleteQueue(queueName);
      } catch (error) {
        if (error instanceof Error && !error.message.includes('NOT_FOUND')) {
          errors.push(new Error(`Failed to delete queue ${queueName}: ${error.message}`));
        }
      }
    }

    // Delete test exchanges
    const exchangesToDelete = [TEST_QUEUES.PUBSUB_EXCHANGE, TEST_QUEUES.ROUTING_EXCHANGE];
    for (const exchangeName of exchangesToDelete) {
      try {
        await channel.deleteExchange(exchangeName);
      } catch (error) {
        if (error instanceof Error && !error.message.includes('NOT_FOUND')) {
          errors.push(new Error(`Failed to delete exchange ${exchangeName}: ${error.message}`));
        }
      }
    }

    client.connectionPool.returnConnection(connection);
  } catch (error) {
    errors.push(new Error(`Failed to cleanup test resources: ${error instanceof Error ? error.message : String(error)}`));
  }

  // Report errors but don't throw to avoid breaking test cleanup
  if (errors.length > 0) {
    console.warn('Cleanup warnings:', errors.map(e => e.message).join('; '));
  }
};

export const waitForMessages = (expectedCount: number, timeout: number = 5000): Promise<any[]> => {
  const messages: any[] = [];
  
  return new Promise((resolve, reject) => {
    const timeoutId = setTimeout(() => {
      reject(new Error(`Timeout waiting for ${expectedCount} messages, got ${messages.length}`));
    }, timeout);
    
    const checkMessages = () => {
      if (messages.length >= expectedCount) {
        clearTimeout(timeoutId);
        resolve(messages);
      } else {
        setTimeout(checkMessages, 100);
      }
    };
    
    checkMessages();
  });
};

export const createMessageCollector = () => {
  const messages: any[] = [];
  const errors: any[] = [];
  
  return {
    messages,
    errors,
    collect: (message: any) => {
      messages.push(message);
    },
    collectError: (error: any) => {
      errors.push(error);
    },
    clear: () => {
      messages.length = 0;
      errors.length = 0;
    },
    waitFor: (count: number, timeout: number = 5000) => {
      return new Promise<any[]>((resolve, reject) => {
        const timeoutId = setTimeout(() => {
          reject(new Error(`Timeout waiting for ${count} messages, got ${messages.length}`));
        }, timeout);
        
        const check = () => {
          if (messages.length >= count) {
            clearTimeout(timeoutId);
            resolve([...messages]);
          } else {
            setTimeout(check, 100);
          }
        };
        
        check();
      });
    },
  };
};

export const generateTestData = {
  userEvent: (userId: string, eventType: string) => ({
    userId,
    eventType,
    timestamp: new Date().toISOString(),
    metadata: {
      source: 'test',
      version: '1.0',
    },
  }),
  
  order: (orderId: string, amount: number) => ({
    orderId,
    amount,
    currency: 'USD',
    items: [
      { id: 'item1', name: 'Test Item', price: amount },
    ],
    customer: {
      id: 'customer123',
      email: 'test@example.com',
    },
    timestamp: Date.now(),
  }),
  
  notification: (type: string, recipient: string) => ({
    type,
    recipient,
    subject: `Test ${type} notification`,
    body: `This is a test ${type} notification for ${recipient}`,
    priority: 'normal',
    timestamp: Date.now(),
  }),
  
  task: (taskType: string, priority: number = 1) => ({
    id: `task_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
    type: taskType,
    priority,
    payload: {
      action: `perform_${taskType}`,
      parameters: {
        param1: 'value1',
        param2: 'value2',
      },
    },
    createdAt: Date.now(),
  }),
};

export const assertMessageStructure = (message: any, expectedStructure: any) => {
  for (const [key, value] of Object.entries(expectedStructure)) {
    expect(message).toHaveProperty(key);
    
    if (typeof value === 'object' && value !== null) {
      assertMessageStructure(message[key], value);
    } else if (typeof value === 'string' && value.startsWith('typeof:')) {
      const expectedType = value.replace('typeof:', '');
      expect(typeof message[key]).toBe(expectedType);
    } else if (value !== undefined) {
      expect(message[key]).toBe(value);
    }
  }
};

export const measurePerformance = async <T>(
  operation: () => Promise<T>,
  name: string = 'operation'
): Promise<{ result: T; duration: number; memoryUsed: number }> => {
  const startTime = process.hrtime.bigint();
  const startMemory = process.memoryUsage().heapUsed;
  
  const result = await operation();
  
  const endTime = process.hrtime.bigint();
  const endMemory = process.memoryUsage().heapUsed;
  
  const duration = Number(endTime - startTime) / 1000000; // Convert to milliseconds
  const memoryUsed = endMemory - startMemory;
  
  console.log(`Performance [${name}]: ${duration.toFixed(2)}ms, Memory: ${(memoryUsed / 1024 / 1024).toFixed(2)}MB`);
  
  return { result, duration, memoryUsed };
};
